package com.zys.http.processor;

import cn.hutool.core.text.CharSequenceUtil;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.impl.source.PsiFieldImpl;
import com.intellij.psi.search.GlobalSearchScope;
import com.zys.http.constant.HttpEnum;
import com.zys.http.entity.param.ParamProperty;
import com.zys.http.entity.velocity.MethodItem;
import com.zys.http.processor.spring.SpringJavaApiProcessor;
import com.zys.http.tool.ThreadTool;
import com.zys.http.ui.tree.node.MethodNode;
import jdk.jfr.Description;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * @author zhou ys
 * @since 2024-05-16
 */
public abstract class AbstractJavaApiProcessor implements ApiProcessor {

    private static final Pattern GENERICS_PATTERN = Pattern.compile("<(.+?)>");

    @Description("包装类")
    protected static final Map<String, Object> BASIC_DATA_TYPE_OBJECT_MAP = new ConcurrentHashMap<>();

    static {
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.lang.Boolean", false);
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.lang.String", "");
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.lang.Byte", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.lang.Short", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.lang.Integer", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.lang.Long", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.lang.Float", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.lang.Double", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.math.BigInteger", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("java.math.BigDecimal", 0.0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("boolean", false);
        BASIC_DATA_TYPE_OBJECT_MAP.put("byte", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("short", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("int", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("long", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("float", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("org.springframework.web.servlet.ModelAndView", Collections.emptyMap());
        BASIC_DATA_TYPE_OBJECT_MAP.put("org.springframework.ui.ModelMap", Collections.emptyMap());
    }

    @Override
    public String packageName(PsiElement psiElement) {
        if (!(psiElement instanceof PsiClass psiClass)) {
            return null;
        }
        PsiJavaFile javaFile = (PsiJavaFile) psiClass.getContainingFile();
        return javaFile.getPackageName();
    }

    @Override
    @SneakyThrows
    public String controllerSwagger(PsiElement psiElement) {
        if (!(psiElement instanceof PsiClass psiClass)) {
            return "";
        }

        PsiModifierList modifierList = psiClass.getModifierList();
        if (Objects.isNull(modifierList)) {
            return "";
        }
        List<HttpEnum.Swagger> swaggers = List.of(HttpEnum.Swagger.API, HttpEnum.Swagger.TAG);

        return getSwagger(modifierList, swaggers);
    }

    @Override
    public List<MethodNode> methodNodes(PsiElement psiElement, String contextPath) {
        if (!(psiElement instanceof PsiClass psiClass)) {
            return Collections.emptyList();
        }
        if (psiClass.isAnnotationType() || psiClass.isInterface() || psiClass.isEnum()) {
            return Collections.emptyList();
        }
        PsiMethod[] methods = psiClass.getAllMethods();
        String controllerPath = controllerPath(psiClass);

        return Arrays.stream(methods).map(method -> createMethodNode(method, controllerPath, contextPath)).filter(Objects::nonNull).toList();
    }

    protected abstract MethodNode createMethodNode(PsiMethod psiElement, String controllerPath, String contextPath);

    @Override
    @SneakyThrows
    public String methodSwagger(PsiElement psiElement) {
        if (!(psiElement instanceof PsiMethod psiMethod)) {
            return "";
        }
        PsiModifierList modifierList = psiMethod.getModifierList();
        List<HttpEnum.Swagger> swaggers = List.of(HttpEnum.Swagger.API_OPERATION, HttpEnum.Swagger.OPERATION);
        return getSwagger(modifierList, swaggers);
    }

    @Override
    public Map<String, ParamProperty> methodParametersParse(@NotNull PsiElement element, boolean isJsonPretty) {
        if (!(element instanceof PsiMethod method)) {
            Map<String, ParamProperty> map = new HashMap<>();
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            return map;
        }
        List<PsiParameter> parameters = methodParameters(method);
        if (parameters.isEmpty()) {
            Map<String, ParamProperty> map = new HashMap<>();
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            return map;
        }
        Map<String, ParamProperty> map = new HashMap<>();
        for (PsiParameter parameter : parameters) {
            parameterParse(parameter, map, isJsonPretty);
        }
        return map;
    }

    protected abstract void parameterParse(@NotNull PsiParameter parameter, @NotNull Map<String, ParamProperty> map,
                                           boolean isJsonPretty);

    @SneakyThrows
    private String getSwagger(PsiModifierList modifierList, List<HttpEnum.Swagger> swaggers) {
        List<PsiAnnotation> list = ReadAction.nonBlocking(() -> Stream.of(modifierList.getAnnotations())
                .filter(o -> swaggers.stream().map(HttpEnum.Swagger::getClazz).toList().contains(o.getQualifiedName()))
                .toList()).submit(ThreadTool.getExecutor()).get();

        if (list.isEmpty()) {
            return "";
        }
        PsiAnnotation annotation = list.get(0);
        HttpEnum.Swagger operation = swaggers.stream().filter(o -> o.getClazz().equals(annotation.getQualifiedName()))
                .findFirst().orElse(null);
        if (Objects.isNull(operation)) {
            return "";
        }
        return annotationValue(annotation, new String[]{operation.getValue()});
    }

    @Override
    public String annotationValue(PsiElement psiElement, String[] attributeNames) {
        if (!(psiElement instanceof PsiAnnotation annotation)) {
            return "";
        }
        List<PsiAnnotationMemberValue> initializerList = new ArrayList<>();
        for (String attributeName : attributeNames) {
            PsiAnnotationMemberValue annoValue = annotation.findAttributeValue(attributeName);
            if (annoValue instanceof PsiArrayInitializerMemberValue arrayAnnoValues) {
                PsiAnnotationMemberValue[] initializers = arrayAnnoValues.getInitializers();
                if (initializers.length > 0) {
                    initializerList.addAll(List.of(initializers));
                }
            } else {
                if (annoValue != null) {
                    initializerList.add(annoValue);
                }
            }
        }
        if (initializerList.isEmpty()) {
            return "";
        }
        PsiAnnotationMemberValue memberValue = initializerList.get(0);
        // 是否是常量值引用, 不作多次引用判断
        String s = stringConstantValue(memberValue);
        if (Objects.nonNull(s)) {
            return s;
        }

        String text = memberValue.getText();
        return text.startsWith("\"") && text.endsWith("\"") ? text.substring(1, text.length() - 1) : text;

    }

    @Override
    public List<MethodItem> postmanMethods(PsiElement psiElement, String contextPath) {
        if (!(psiElement instanceof PsiClass psiClass)) {
            return Collections.emptyList();
        }
        String controllerPath = controllerPath(psiClass);
        PsiMethod[] methods = psiClass.getAllMethods();
        if (methods.length < 1) {
            return Collections.emptyList();
        }
        List<MethodItem> methodItems = new ArrayList<>();
        for (PsiMethod method : methods) {
            MethodItem methodItem = postmanMethodItem(method, contextPath, controllerPath);
            if (Objects.nonNull(methodItem)) {
                methodItems.add(methodItem);
            }
        }
        return methodItems;
    }

    protected abstract MethodItem postmanMethodItem(PsiMethod method, String contextPath, String controllerPath);

    private String stringConstantValue(PsiAnnotationMemberValue memberValue) {
        if (Objects.isNull(memberValue)) {
            return null;
        }
        if (!(memberValue instanceof PsiReferenceExpression expression)) {
            return null;
        }
        if (!(expression.resolve() instanceof PsiFieldImpl field)) {
            return null;
        }

        PsiExpression initializer = field.getInitializer();
        if (Objects.nonNull(initializer)) {
            String text = initializer.getText();
            return text.startsWith("\"") && text.endsWith("\"") ? text.substring(1, text.length() - 1) : text;
        }
        return null;
    }

    public List<PsiParameter> methodParameters(PsiMethod psiMethod) {
        PsiParameterList parameterList = psiMethod.getParameterList();
        if (parameterList.isEmpty()) {
            return Collections.emptyList();
        }
        PsiParameter[] parameters = parameterList.getParameters();
        return List.of(parameters);
    }

    /**
     * 获取 JavaBean 属性
     *
     * @param psiClass /
     * @return /
     */
    public List<PsiField> classMemberFields(@NotNull PsiClass psiClass) {
        return Arrays.stream(psiClass.getAllFields())
                .filter(field -> !hasStaticModifier(field.getModifierList()))
                .filter(field -> !hasPublicModifier(field.getModifierList()))
                .toList();
    }

    public boolean hasStaticModifier(@Nullable PsiModifierList target) {
        return hasModifier(target, PsiModifier.STATIC);
    }

    public boolean hasPublicModifier(@Nullable PsiModifierList target) {
        return hasModifier(target, PsiModifier.PUBLIC);
    }

    private boolean hasModifier(@Nullable PsiModifierList target, @PsiModifier.ModifierConstant @NotNull String modifier) {
        return Objects.nonNull(target) && target.hasModifierProperty(modifier);
    }

    /**
     * 获取泛型字符串
     *
     * @param psiType /
     * @return /
     */
    public String genericsType(@NotNull PsiType psiType) {
        String canonicalText = psiType.getCanonicalText();
        Matcher matcher = GENERICS_PATTERN.matcher(canonicalText);
        return matcher.find() ? matcher.group(1) : "";
    }

    @Override
    public Object typeDefaultValue(Project project, Cloneable type) {
        if (!(type instanceof PsiType psiType)) {
            return null;
        }
        return typeDefaultValue(project, psiType, new HashMap<>());
    }

    private @Nullable Object typeDefaultValue(Project project, PsiType psiType, Map<String, Integer> recursionMap) {
        final String canonicalText = psiType.getCanonicalText();
        // 基元类型对应的包装类
        Object value = BASIC_DATA_TYPE_OBJECT_MAP.get(canonicalText);
        if (Objects.nonNull(value)) {
            return value;
        }

        if (Object.class.getName().equals(canonicalText)) {
            return Collections.emptyMap();
        }
        // 应该是基元类型和包装类才返回空数组类型,如果是类应该处理
        Object arrayResult = arrayTypeDefaultValue(project, psiType, recursionMap);
        if (Objects.nonNull(arrayResult)) {
            return arrayResult;
        }

        Object collectionsResult = collectionTypeDefaultValue(project, psiType, recursionMap);
        if (Objects.nonNull(collectionsResult)) {
            return collectionsResult;
        }

        if (psiType instanceof PsiClassReferenceType type) {
            // 处理实体类类型
            PsiClass psiClass = type.resolve();

            if (Objects.isNull(psiClass)) {
                return null;
            }
            if (psiClass.isEnum()) {
                return Arrays.stream(psiClass.getFields()).filter(PsiEnumConstant.class::isInstance)
                        .map(PsiField::getName)
                        .findFirst().orElse("");
            }
            // 对几个比较常用的类型进行特殊处理
            Object hasResult = dateTypeDefaultValue(psiClass);
            if (Objects.nonNull(hasResult)) {
                return hasResult;
            }
            // 类所有对象属性
            Map<String, Object> result = new LinkedHashMap<>();
            List<PsiField> objectProperties = classMemberFields(psiClass);
            Integer orDefault = recursionMap.getOrDefault(psiType.getCanonicalText(), 0);
            if (orDefault < 2) {
                orDefault = orDefault + 1;
                recursionMap.put(psiType.getCanonicalText(), orDefault);
                for (PsiField property : objectProperties) {
                    String propertyName = property.getName();
                    // 是否有 @JsonProperty 属性
                    if (property.hasAnnotation(JSON_PROPERTY_ANNO_FQN)) {
                        propertyName = annotationValue(property.getAnnotation(JSON_PROPERTY_ANNO_FQN), new String[]{"value"});
                    }

                    result.put(propertyName, typeDefaultValue(project, property.getType(), recursionMap));
                }
            }
            return result;
        }
        return null;
    }

    @Description("处理数组类型")
    private @Nullable Object arrayTypeDefaultValue(Project project, @NotNull PsiType psiType, Map<String, Integer> recursionMap) {
        String canonicalText = psiType.getCanonicalText();
        if (canonicalText.contains("[]")) {
            String arrayCanonicalText = canonicalText.substring(0, canonicalText.indexOf("["));
            if (Object.class.getName().equals(arrayCanonicalText)) {
                return EMPTY_ARRAY;
            }
            // 是否是基元类型或对应包装类型的数组
            Object o = BASIC_DATA_TYPE_OBJECT_MAP.get(arrayCanonicalText);
            if (Objects.nonNull(o)) {
                return List.of(o);
            }
            PsiClassType type = PsiType.getTypeByName(arrayCanonicalText, project, GlobalSearchScope.allScope(project));
            Object defaultValue = typeDefaultValue(project, type, recursionMap);
            return Objects.isNull(defaultValue) ? EMPTY_ARRAY : List.of(defaultValue);
        }
        return null;
    }

    @Description("处理集合类型")
    private @Nullable Object collectionTypeDefaultValue(Project project, @NotNull PsiType psiType, Map<String, Integer> recursionMap) {
        final String canonicalText = psiType.getCanonicalText();
        if (!canonicalText.startsWith("java.util.")) {
            return null;
        }
        // Map 直接返回空 Map, 反正不知道 key
        if (canonicalText.contains("Map")) {
            return Collections.emptyMap();
        }
        // 如果是 List/Set 考虑泛型
        if (canonicalText.contains("List") || canonicalText.contains("Set")) {
            String genericsType = SpringJavaApiProcessor.INSTANCE.genericsType(psiType);
            // 如果泛型是 java.lang.Object
            if (Object.class.getName().equals(genericsType)) {
                return EMPTY_ARRAY;
            }
            // ? extends ?, ? super ?
            if (CharSequenceUtil.isNotEmpty(genericsType) && (genericsType.contains(" extends ") || genericsType.contains(" super "))) {
                return EMPTY_ARRAY;
            }
            Object o = BASIC_DATA_TYPE_OBJECT_MAP.get(genericsType);
            if (Objects.nonNull(o)) {
                // 说明是基元类型
                return List.of(o);
            }
            PsiClassType type = PsiType.getTypeByName(Objects.requireNonNull(genericsType), project, GlobalSearchScope.allScope(project));

            Object defaultValue = typeDefaultValue(project, type, recursionMap);
            return Objects.isNull(defaultValue) ? EMPTY_ARRAY : List.of(defaultValue);
        }
        return null;
    }

    @Description("处理日期类型")
    private @Nullable @Unmodifiable Object dateTypeDefaultValue(PsiClass psiClass) {
        if (Objects.isNull(psiClass)) {
            return null;
        }
        String qualifiedName = psiClass.getQualifiedName();
        if (Objects.isNull(qualifiedName)) {
            return null;
        }
        if (qualifiedName.equals(Date.class.getName()) || qualifiedName.equals(LocalDateTime.class.getName())) {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
        }
        if (qualifiedName.equals(LocalDate.class.getName())) {
            return new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
        }
        return null;
    }
}
